package com.atguigu.tingshu.common.login.aspect;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.login.annotation.TingShuLogin;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * AOP认证中心
 */
@Component
@Aspect
public class loginAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Around(value = "@annotation(com.atguigu.tingshu.common.login.annotation.TingShuLogin)")
    public Object checkLogin(ProceedingJoinPoint pjp) throws Throwable{
        //前置通知
        String accessToken = getToken();
        //判断登录注解属性值
        Boolean isLogin = getLoginSwitch(pjp);
        if (!isLogin){
            //如果为false,则不需要认证
            if (!StringUtils.hasText(accessToken)){
                return pjp.proceed();
            }
        }
       //校验redis中的token与前端提交的是否相等,如果相等,进行验签并解析
        Long userId = checkAccessTokenAndGetUserId(accessToken);
        AuthContextHolder.setUserId(userId);
        Object retVal;//执行目标方法
        try {
            retVal = pjp.proceed();
        } finally {
            AuthContextHolder.removeUserId();
        }
        //后置通知
        return retVal;
    }

    /**
     * 获取登录注解属性值
     * @param pjp ProceedingJoinPoint
     * @return Boolean
     */
    private Boolean getLoginSwitch(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        TingShuLogin annotation = method.getAnnotation(TingShuLogin.class);
        return annotation.required();
    }

    /**
     * 校验redis中的token与前端提交的是否相等,如果相等,进行验签并解析
     * @param accessToken accessToken
     * @return Long
     */
    private Long checkAccessTokenAndGetUserId(String accessToken) {
        if (!StringUtils.hasText(accessToken)){
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        Jwt jwt = JwtHelper.decodeAndVerify(accessToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));
        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        String openId = (String) map.get("openId");
        Object userId =  map.get("userId");

        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + "access" + openId;
        String tokenFromRedis = redisTemplate.opsForValue().get(accessTokenKey);
        if (!StringUtils.hasText(tokenFromRedis) || !tokenFromRedis.equals(accessToken)){
            throw new GuiguException(201,"accessToken过期了");
        }

        return Long.parseLong(userId.toString());
    }

    /**
     * 获取token
     * @return String
     */
    private String  getToken() {
        //获取请求头在的token
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //从请求对象中获取token,验签并解析负载数据
        return requestAttributes.getRequest().getHeader("token");
    }
}
